/*
 * lookup functionalty using asynchronous getaddrinfo_a() system call
 */

#include <sys/epoll.h>
#include "lookuputils.h"

#define MAXEVENTS	10

static const char *progname;

struct gaicb **reqs;
int nreqs = 0;

static void
usage_alookup_client(const int exit_code)
{
    fprintf(stderr, "%s: -s <server address> -p <server port> -f <name to ip lookup> [ -r <ip to name lookup> ] [ -h <help> ]\n", progname);
    exit(exit_code);
}

static void
usage_alookup(const int exit_code)
{
    fprintf(stderr, "%s: -p <port> [ -h <help> ]\n", progname);
    exit(exit_code);
}

static void
name_to_ip(const char *hostname, char *ip)
{
    int ret;
    char ipaddr[NI_MAXHOST];
    struct gaicb *reqs[1];
    struct addrinfo *res;

    reqs[0] = malloc(sizeof(struct gaicb));
    if (reqs[0] == NULL)
	error_msg("malloc()");
    memset(reqs[0], 0, sizeof(struct gaicb));
    reqs[0]->ar_name = hostname;

    getaddrinfo_a(GAI_NOWAIT, reqs, 1, NULL);
    
    for(;;) { 
	ret = gai_error(reqs[0]);
	if (ret == 0) { 
	    res = reqs[0]->ar_result;
	    ret = getnameinfo(res->ai_addr, res->ai_addrlen, ipaddr, sizeof(ipaddr), NULL, 0, NI_NUMERICHOST);
	    if (ret != 0) {
		fprintf(stderr, "getnameinfo() failed for %s: %s: %m\n", hostname, gai_strerror(ret));
		exit(1);
	    }
	    strcpy(ip, ipaddr);
	    free(reqs[0]);
	    return;
	} else if (ret == EAI_INPROGRESS) {
	    continue;
	} else {
	    fprintf(stderr, "getnameinfo_a() failed for %s: %s: %m\n", hostname, gai_strerror(ret));
	    strcpy(ip, gai_strerror(ret));
	    free(reqs[0]);
	    return;
	}
    }
}

static void
server_stuff(int sock)
{
    int n;
    char buffer[BUFFERSIZE];
    char ip[BUFFERSIZE];

    memset(buffer, 0, sizeof(buffer));
    n = read(sock, buffer, BUFFERSIZE);
    if (n == -1) {
	printf("fd: %d\n", sock);
	error_msg("read(): fail to read from client");
    }

    memset(ip, 0, BUFFERSIZE);

    name_to_ip(buffer, ip);

    n = write(sock, ip, strlen(ip));
    if (n == -1)
	error_msg("write(): fail to write on client");
}

static const char *
mybasename(const char *path)
{
    const char *name;

    if(path) {
	name = strrchr(path, '/');
	if (name) {
	    return ++name;
	}
    }
    return path;
}

static int
alookup(int argc, char **argv)
{
    int optch, srv_sock, cli_sock;
    size_t alen;
    struct sockaddr_in cli_addr;
    char *port = NULL;

    struct epoll_event ev, events[MAXEVENTS];
    int n, nfds, epollfd;

    while ((optch = getopt(argc, argv, "p:h")) != -1) {
	switch (optch) {
	    case 'p':
		port = strdup(optarg);
		break;
	    case 'h':
		usage_alookup(0);
		break;
	    default:
		usage_alookup(1);
		break;
	}
    }

    if (!port)
	usage_alookup(1);

    srv_sock = create_tcp_server_socket(port, 5);

    epollfd = epoll_create(MAXEVENTS);
    if (epollfd == -1)
	error_msg("epoll_crearte(): fail in epoll_create");

    ev.events = EPOLLIN;
    ev.data.fd = srv_sock;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, srv_sock, &ev) == -1)
	error_msg("epoll_ctl: fail in epoll_ctl");

    for (;;) {
	nfds = epoll_wait(epollfd, events, MAXEVENTS, -1);
	if (nfds == -1)
	    error_msg("epoll_wait(): fail in epoll_wait");

	for (n = 0; n < nfds; ++n) {
	    if (events[n].data.fd == srv_sock) {
		cli_sock = accept(srv_sock, (struct sockaddr *) &cli_addr, &alen);
		if (cli_sock < 0)
		    error_msg("accept(): fail to accept connection");
		
		make_socket_nonblocking(cli_sock);

		ev.events = EPOLLIN;
		ev.data.fd = cli_sock;

		if (epoll_ctl(epollfd, EPOLL_CTL_ADD, cli_sock, &ev) == -1)
		    error_msg("epoll_ctl: fail in epoll_ctl");
	    } else {
		server_stuff(events[n].data.fd);
		if (epoll_ctl(epollfd, EPOLL_CTL_DEL, events[n].data.fd, NULL) == -1)
		    error_msg("epoll_ctl: fail in epoll_ctl");
		close(events[n].data.fd);
	    }
	}
    }

    close(srv_sock);

    return 0;
}

static int
alookup_client(int argc, char **argv)
{
    int optch;
    int  sockfd, n;
    char buffer[BUFFERSIZE];
    char *host = NULL;
    char *ip = NULL;
    char *serverip = NULL;
    char *port = NULL;
    
    while ((optch = getopt(argc, argv, "f:r:s:p:h")) != -1) {
	switch (optch) {
	    case 'f':
		host = optarg;
		break;
	    case 'r':
		ip = optarg;
		break;
	    case 's':
		serverip = optarg;
		break;
	    case 'p':
		port = optarg;
		break;
	    case 'h':
		usage_alookup_client(0);
		break;
	    default:
		usage_alookup_client(1);
		break;
	}
    }

    if (!serverip || !port || !host)
	usage_alookup_client(1);

    sockfd = create_tcp_socket();
    connect_to_tcp_server(sockfd, serverip, port); 

    /* Send host name to server */
    n = write(sockfd, host, strlen(host));
    if (n == -1)
	error_msg("ERROR: Writing to socket");

    /* Get responce from the server */
    memset(buffer, 0, BUFFERSIZE);
    n = read(sockfd, buffer, BUFFERSIZE);
    if (n == -1)
	error_msg("ERROR: Reading from server");

    printf("%s: %s\n", host, buffer);

    return 0;
}

int main(int argc, char **argv)
{
    progname = mybasename(argv[0]);

    if (strcmp(progname, "alookup") == 0)
	return alookup(argc, argv);
    else if (strcmp(progname, "alookup_client") == 0)
	return alookup_client(argc, argv);
    else {
	fprintf(stderr, "ERROR: Invalid program name\n");
	exit(1);
    }

    return 0;
}

